﻿using Serilog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// 进一步封装RedisHelper
/// </summary>
public class CsRedisHelper : RedisHelper<CsRedisHelper>
{
    private static ConcurrentDictionary<string, SemaphoreSlim> _limiterDic = new ConcurrentDictionary<string, SemaphoreSlim>();
    static DateTime _dt1970 = new DateTime(1970, 1, 1);

    /// <summary>
    /// 此方法redis操作不使用异步,只在获取数据异步
    /// </summary>
    async public static Task<T> CacheShellzAsync<T>(string key, int timeoutSeconds, Func<Task<T>> getDataAsync)
    {
        if (timeoutSeconds == 0) return await getDataAsync();
        T cacheValue;
        try
        {
            cacheValue = Get<T>(key);
        }
        catch (Exception)
        {
            Del(key);
            throw;
        }
        if (cacheValue != null)
        {
            return cacheValue;
        }
        else
        {
            var ret = await getDataAsync();
            Set(key, ret, timeoutSeconds);
            return ret;
        }
    }

    /// <summary>
    /// 此方法redis操作不使用异步,只在获取数据异步
    /// </summary>
    async public static Task<T> CacheShellzAsync<T>(string key, string field, int timeoutSeconds, Func<Task<T>> getDataAsync)
    {
        if (timeoutSeconds == 0) return await getDataAsync();
        try
        {
            var value = HGet<(T, long)>(key, field);
            if (DateTime.Now.Subtract(_dt1970.AddSeconds(value.Item2)).TotalSeconds <= timeoutSeconds) return value.Item1;
        }
        catch (Exception)
        {
            HDel(key, field);
            throw;
        }

        var ret = await getDataAsync();
        HSet(key, field, (ret, (long)DateTime.Now.Subtract(_dt1970).TotalSeconds));
        return ret;
    }

    /// <summary>
    /// 查询长时间不使用的key
    /// </summary>
    /// <param name="idleSeconds">查询空闲时间大于idleSeconds的key</param>
    /// <param name="delIdleKey">是否删除</param>
    /// <returns></returns>
    public static List<string> QueryLongTimeNotUseKey(double idleSeconds = 0, bool delIdleKey = false)
    {
        var pool = Nodes.First().Value;
        var prefix = pool.Prefix ?? string.Empty;
        var pattern = $"{prefix}*";
        long cursor = 0;
        List<string> keys = new List<string>();
        while (true)
        {
            var redisScan = Scan(cursor, pattern, 100);
            if (redisScan == null || redisScan.Cursor == 0) break;
            cursor = redisScan.Cursor;
            if (redisScan.Items.Length > 0)
            {
                keys.AddRange(redisScan.Items);
            }
        }
        if (idleSeconds > 0)
        {
            keys = keys.AsParallel().Where(x =>
            {
                if (!x.Contains(prefix)) return false;
                var key = x.Replace(prefix, "");
                //只有当maxmemory-policy被设置为LRU或noeviction时，该子命令才可以使用
                var s = ObjectIdleTime(key);
                if (s >= idleSeconds)
                {
                    if (delIdleKey)
                    {
                        Del(key);
                    }
                    return true;
                }
                return false;
            }).ToList();
        }
        return keys;
    }

    /// <summary>
    /// 限流
    /// </summary>
    /// <param name="key">key,需要包含前缀</param>
    /// <param name="limitNum">限流数量</param>
    /// <param name="limitSeconds">限流时间(秒)</param>
    /// <returns></returns>
    public static async Task RateLimitAndWaitAsync(string key, int limitNum = 100, int limitSeconds = 1)
    {
        var semaphore = _limiterDic.GetOrAdd(key, x => new SemaphoreSlim(limitNum));
        try
        {
            //单进程限流,减少redis调用
            await semaphore.WaitAsync().ConfigureAwait(false);

            //redis限流
            var delayTime = limitSeconds * 1000;
            while (IsRateLimit(key, limitNum, limitSeconds))
            {
                await Task.Delay(delayTime).ConfigureAwait(false);
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex, $"RateLimitAndWaitAsync限流异常,{key}");
        }
        finally
        {
            semaphore.Release();
        }
    }

    private static bool IsRateLimit(string key, int limitNum = 100, int expireSeconds = 1)
    {
        const string script = @"
                local current
                current = redis.call(""incr"",KEYS[1])
                if current == 1 then
                    redis.call(""expire"", KEYS[1], ARGV[1])
                end
                return current
                ";
        var num = (long?)RedisHelper.Eval(script, key, expireSeconds);
        return num > limitNum;
    }
}
